package com.xiezc;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.xiezc.model.Device;
import com.xiezc.model.PTypeEnum;
import com.xiezc.model.Packet;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;

import static com.xiezc.Xutil.selfDeviceId;

/**
 * @author wb-xzc291800
 * @date 2018/09/05 15:15
 */
@Slf4j
public class RecvPacketService {
    private RecvPacketService(Map<Long, Device> deviceMap) {
        this.deviceMap = deviceMap;

    }

    @Getter
    private ConcurrentHashMap<Long, Set<RecvPacket>> recvPacketBuf = new ConcurrentHashMap<>();

    static RecvPacketService recvPacketService;

    @Setter
    private SendService sendService;

    @Setter
    private RecvService recvService;

    @Setter
    private RetPacketService retPacketService;



    final Map<Long, Device> deviceMap;

    @Synchronized
    static RecvPacketService single(Map<Long, Device> deviceMap) {
        if (recvPacketService == null) {
            recvPacketService = new RecvPacketService(deviceMap);
        }
        log.info("RecvPacketService 收到的包解析服务初始化完成....");
        return recvPacketService;
    }

    void start() {
        Executors.newSingleThreadExecutor().execute(() -> {
            while (true) {
                try {
                    RecvObj take = recvService.poll(1000, TimeUnit.MILLISECONDS);
                    if (take == null) {
                        //  log.error("接收到的recvObj 是null");
                        continue;
                    }
                    ByteBuffer byteBuffer = take.getByteBuffer();
                    InetSocketAddress inetSocketAddress = take.getInetSocketAddress();

                    Packet packet = resovePacket(byteBuffer);
                    PTypeEnum ptype = packet.getPtype();
                    if (ptype == PTypeEnum.Find) {
                        dealFind(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Find_ret) {
                        dealFindRet(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Login) {
                        dealLogin(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Login_ret) {
                        dealLoginRet(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Heart) {
                        dealHeart(inetSocketAddress, packet);
                        continue;
                    }

                    if (ptype == PTypeEnum.Heart_ret) {
                        dealHeartRet(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.File) {
                        dealFileAndMsg(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.File_ret) {
                        dealFileAndMsgRet(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Msg) {
                        dealFileAndMsg(inetSocketAddress, packet);
                        continue;
                    }
                    if (ptype == PTypeEnum.Msg_ret) {
                        dealFileAndMsgRet(inetSocketAddress, packet);
                        continue;
                    }

                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                }
            }
        });

    }

    /**
     * 处理心跳回复包. token对, sender在册, 则更新在线信息
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealHeartRet(InetSocketAddress inetSocketAddress, Packet packet) {
        CheckPacket checkPacket = new CheckPacket(packet, inetSocketAddress).invoke();
        log.info("收到心跳回复包, senderId:{}, address:{}", packet.getDeviceId(), inetSocketAddress);
    }

    /**
     * 心跳包不用重发
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealHeart(InetSocketAddress inetSocketAddress, Packet packet) {
        CheckPacket checkPacket = new CheckPacket(packet, inetSocketAddress).invoke();
        Device sender = checkPacket.getSender();
        //发送者是否登录过
        if (!sender.getConneted()) {
            //没有登录的消息, 不予理会, 回复包不发
            return;
        }

        //发送者在册, 并且token对的
        if (checkPacket.isRecord() && checkPacket.okToken()) {
            log.info("收到心跳包, senderId:{}, address:{}", packet.getDeviceId(), inetSocketAddress);
            Packet retPacket = checkPacket.getRetPacket();
            //发送回复包
            sendService.addBuf(retPacket, checkPacket.getSender());
        }
    }

    /**
     * 文件处理包.
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealFileAndMsg(InetSocketAddress inetSocketAddress, Packet packet) {
        CheckPacket checkPacket = new CheckPacket(packet, inetSocketAddress).invoke();
        Device sender = checkPacket.getSender();
        //发送者是否登录过
        if (!sender.getConneted()) {
            //没有登录的消息, 不予理会, 回复包不发
            return;
        }

        //发送者在册, 并且token对的
        if (checkPacket.isRecord() && checkPacket.okToken()) {
            //回复包
            sendService.addBuf(checkPacket.getRetPacket(), checkPacket.getSender());
            //放入缓存中
            Set<RecvPacket> packetSet = recvPacketBuf.get(packet.getMsgId());
            if (packetSet == null) {
                packetSet = Collections.synchronizedSet(new HashSet<RecvPacket>());
                recvPacketBuf.put(packet.getMsgId(), packetSet);
            }

            RecvPacket recvPacket = new RecvPacket();
            BeanUtils.copyProperties(packet, recvPacket);
            recvPacket.setSender(sender);
            packetSet.add(recvPacket);
        } else {
            log.warn("发送者不在册或者 token 不对....");
        }

    }

    /**
     * 文件处理包
     *
     * @param inetSocketAddress
     * @param packet
     * @return
     */
    private void dealFileAndMsgRet(InetSocketAddress inetSocketAddress, Packet packet) {
        CheckPacket checkPacket = new CheckPacket(packet, inetSocketAddress).invoke();
        Device sender = checkPacket.getSender();
        //发送者是否登录过
        if (!sender.getConneted()) {
            //没有登录的消息, 不予理会, 回复包不发
            return;
        }

        //令牌对上了
        if (checkPacket.isRecord() && checkPacket.okToken()) {
            retPacketService.removePacket(packet);
        }
    }

    /**
     * 处理登录回复消息
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealLoginRet(InetSocketAddress inetSocketAddress, Packet packet) {
        CheckPacket checkPacket = new CheckPacket(packet, inetSocketAddress).invoke();
        if (!checkPacket.isRecord()) {
            //先放进去, 登录成功还是失败, 下面再判断
            deviceMap.put(packet.getDeviceId(), checkPacket.sender);
        }

        //登录失败
        if (checkPacket.getToken() == 0) {
            log.warn("登录失败的 回复包..... ");
        } else {
            //登录成功
            checkPacket.sender.setToken(packet.getToken());
            checkPacket.sender.setConneted(true);
            log.warn("登录成功, {} ",checkPacket.sender);
        }
        //去掉重发的包
        retPacketService.removePacket(packet);

    }

    /**
     * 处理登录消息
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealLogin(InetSocketAddress inetSocketAddress, Packet packet) {

        Long deviceId = packet.getDeviceId();
        Device sender = deviceMap.get(deviceId);
        sender.setAddress(inetSocketAddress);
        //放入缓存中
        Set<RecvPacket> packetSet = recvPacketBuf.get(packet.getMsgId());
        if (packetSet == null) {
            packetSet = Collections.synchronizedSet(new HashSet<RecvPacket>());
            recvPacketBuf.put(packet.getMsgId(), packetSet);
        }

        RecvPacket recvPacket = new RecvPacket();
        BeanUtils.copyProperties(packet, recvPacket);
        recvPacket.setSender(sender);
        packetSet.add(recvPacket);
    }

    /**
     * 处理搜寻联结者的回复消息
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealFindRet(InetSocketAddress inetSocketAddress, Packet packet) {
        Long deviceId = packet.getDeviceId();
        Device device = deviceMap.get(deviceId);
        //在册记录
        if (Objects.isNull(device)) {
            device = new Device();
            device.setId(deviceId);
            deviceMap.put(deviceId, device);
        }

        device.setAddress(inetSocketAddress);
        device.refreshTime();
    }

    /**
     * 处理搜寻联结者的消息
     *
     * @param inetSocketAddress
     * @param packet
     */
    private void dealFind(InetSocketAddress inetSocketAddress, Packet packet) {
        Device sender = new Device();
        sender.setId(packet.getDeviceId());
        sender.setAddress(inetSocketAddress);
        Device device = deviceMap.get(selfDeviceId);
        packet.setDeviceId(device.getId());
        packet.setPtype(PTypeEnum.Find_ret);
        sendService.addBuf(packet, sender);
    }

    /**
     * @param byteBuffer
     * @return
     */
    private Packet resovePacket(ByteBuffer byteBuffer) {
        //1. 包类型
        byte Pakettype = byteBuffer.get();
        PTypeEnum pType = PTypeEnum.getByVal(Pakettype);
        if (Objects.isNull(pType)) {
            log.warn("异常包, 类型不对");
            Objects.requireNonNull(null, "异常包, 类型不对");
        }

        Packet packet = new Packet();
        packet.setPtype(pType);
        //2. 设备的id
        packet.setDeviceId(byteBuffer.getLong());
        //3. 令牌
        packet.setToken(byteBuffer.getLong());
        //4. 消息的msgId
        packet.setMsgId(byteBuffer.getLong());
        //5, 回复包索引
        packet.setIndex(byteBuffer.getLong());
        //6.回复包长度
        packet.setLen(byteBuffer.getLong());
        //7. 包的md5
        byte[] packMd5 = new byte[16];
        byteBuffer.get(packMd5);
        packet.setPackMd5(packMd5);
        //8. 包的内容
        int i = byteBuffer.limit() - byteBuffer.position();
        //空包
        if (i == 0) {
            packet.setContent(null);
            return packet;
        }
        byte[] array = new byte[i];
        byteBuffer.get(array);
        packet.setContent(array);
        if (Arrays.equals(Xutil.md5(array), packMd5)) {
            return packet;
        }
        log.warn("包已经损坏, md5校验没有通过");
        return null;
    }

    private class CheckPacket {

        private boolean realSender;
        private Packet packet;
        private long token;
        private Device sender;
        private InetSocketAddress inetSocketAddress;

        private Packet retPacket;

        Packet getRetPacket() {
            return retPacket;
        }

        private boolean trueToken = false;

        public CheckPacket(Packet packet, InetSocketAddress inetSocketAddress) {
            this.packet = packet;
            this.inetSocketAddress = inetSocketAddress;
        }

        boolean isRecord() {return realSender;}

        boolean okToken() {
            return trueToken;
        }

        public long getToken() {
            return token;
        }

        public Device getSender() {
            return sender;
        }

        public CheckPacket invoke() {
            long deviceId = packet.getDeviceId();
            token = packet.getToken();
            sender = deviceMap.get(deviceId);
            if (sender == null) {
                log.error("包的的发送设备不在册.....");
                sender = new Device();
                sender.setAddress(inetSocketAddress);
                sender.setId(deviceId);
                realSender = false;
                return this;
            } else {
                //发送者在册. 构造回复包
                if (!packet.getPtype().isRet()) {
                    Device self = deviceMap.get(selfDeviceId);
                    retPacket = new Packet();
                    BeanUtils.copyProperties(packet, retPacket, "content", "ptype", "deviceId");
                    //回复包的构造
                    retPacket.setPtype(packet.getPtype().getRet());
                    retPacket.setDeviceId(self.getId());
                }
                //判断token对不对
                trueToken = Objects.equals(sender.getToken(), token);
                if (trueToken) {
                    sender.setAddress(inetSocketAddress);
                }
                realSender = true;
            }

            return this;
        }
    }
}

/**
 * @author wb-xzc291800
 * @date 2018/09/07 18:11
 */

@Data
class RecvPacket extends Packet implements Comparable<RecvPacket> {

    Device sender;

    long time;

    public RecvPacket() {
        time = System.currentTimeMillis();
    }

    @Override
    public int compareTo(RecvPacket o) {
        return (int)(this.getIndex() - o.getIndex());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (!(o instanceof RecvPacket)) { return false; }
        if (!super.equals(o)) { return false; }

        RecvPacket that = (RecvPacket)o;

        return getSender() != null ? getSender().equals(that.getSender()) : that.getSender() == null;
    }

    @Override
    public int hashCode() {
        int result = super.hashCode();
        result = 31 * result + (getSender() != null ? getSender().hashCode() : 0);
        return result;
    }
}
